﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using JasLib.Extensions;

namespace JasLib.Wpf
{
    [Flags]
    public enum AnchoringCanvasAnchor : int
    {
        None = 0,
        Left = 1,
        Right = 2,
        Top = 4,
        Bottom = 8,
        All = Left | Right | Top | Bottom,
    }

    /// <summary>
    /// A canvas that provides the familiar anchoring support found in WinForms controls.
    /// </summary>
    public class AnchoringCanvas : Canvas
    {
        #region Anchor

        public readonly static DependencyProperty AnchorProperty = DependencyProperty.RegisterAttached(
            "Anchor",
            typeof(AnchoringCanvasAnchor),
            typeof(AnchoringCanvas),
            new FrameworkPropertyMetadata(
                AnchoringCanvasAnchor.None,
                FrameworkPropertyMetadataOptions.None, // The property helps the control merely react to positioning.
                new PropertyChangedCallback(AnchorProperty_Changed),
                new CoerceValueCallback(AnchorProperty_Coerce)));

        public static void SetAnchor(UIElement element, AnchoringCanvasAnchor anchor)
        {
            element.SetValue(AnchorProperty, anchor);
            return;
        }

        public static AnchoringCanvasAnchor GetAnchor(UIElement element)
        {
            return (AnchoringCanvasAnchor)element.GetValue(AnchorProperty);
        }

        private static void AnchorProperty_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
        }

        private static object AnchorProperty_Coerce(DependencyObject d, object newValue)
        {
            return newValue;
        }

        #endregion

        protected override Size MeasureOverride(Size constraint)
        {
            /// The Canvas class doesn't do squat in its measuring so we're fine just reusing it.
            return base.MeasureOverride(constraint);
        }

        /// <summary>
        /// This method code is generally copied from Canvas.ArrangeOverride().
        /// </summary>
        protected static Point GetConfiguredCanvasPosition(Size arrangeSize, UIElement element)
        {
            if (element == null)
                return new Point();

            double x = 0.0;
            double y = 0.0;
            double left = GetLeft(element);
            if (!double.IsNaN(left))
            {
                x = left;
            }
            else
            {
                double right = GetRight(element);
                if (!double.IsNaN(right))
                {
                    x = (arrangeSize.Width - element.DesiredSize.Width) - right;
                }
            }
            double top = GetTop(element);
            if (!double.IsNaN(top))
            {
                y = top;
            }
            else
            {
                double bottom = GetBottom(element);
                if (!double.IsNaN(bottom))
                {
                    y = (arrangeSize.Height - element.DesiredSize.Height) - bottom;
                }
            }
            return new Point(x, y);
        }

        private Size? _lastArrangedSize = null;
        private Dictionary<UIElement, Rect> _previousArrangedPositions = new Dictionary<UIElement, Rect>();

        protected override Size ArrangeOverride(Size arrangeSize)
        {
            foreach (UIElement element in InternalChildren)
            {
                if (element == null)
                    continue;

                AnchoringCanvasAnchor anchor = GetAnchor(element);
                Rect rcActual;

                /// Apply anchoring if the position has changed at least once.
                if (anchor != AnchoringCanvasAnchor.None &&
                    _lastArrangedSize.HasValue &&
                    _previousArrangedPositions.TryGetValue(element, out rcActual))
                {
                    if ((anchor & AnchoringCanvasAnchor.Right) == AnchoringCanvasAnchor.Right)
                    {
                        if ((anchor & AnchoringCanvasAnchor.Left) == AnchoringCanvasAnchor.Left)
                        {
                            /// The X stays the same, but we stretch the whole thing.
                            rcActual.Width += arrangeSize.Width - _lastArrangedSize.Value.Width;
                        }
                        else
                        {
                            rcActual.X += arrangeSize.Width - _lastArrangedSize.Value.Width;
                        }
                    }
                }
                else
                {
                    rcActual = new Rect(GetConfiguredCanvasPosition(arrangeSize, element), element.DesiredSize);
                }

                if (anchor != AnchoringCanvasAnchor.None)
                    _previousArrangedPositions.Overwrite(element, rcActual);

                Trace.WriteLine("{0} inside {1}".FormatWith(rcActual, arrangeSize));

                if (rcActual.Width < 0 || rcActual.Height < 0)
                    element.Arrange(new Rect());
                else
                    element.Arrange(rcActual);
            }

            _lastArrangedSize = arrangeSize;
            return arrangeSize;
        }
    }
}
